package org.redis.conf;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

/**
 * redis配置类
 * @program: springbootdemo
 * @Date: 2019/1/25 15:20
 * @Author: Mr.Zheng
 * @Description:
 */
@Slf4j
@Configuration
@EnableCaching
public class RedisConfig implements CachingConfigurer {

    @Resource
    private RedisConnectionFactory redisConnectionFactory;
        /**
         * 重写Redis序列化方式，使用Json方式:
         * 当我们的数据存储到Redis的时候，我们的键（key）和值（value）都是通过Spring提供的Serializer序列化到数据库的。RedisTemplate默认使用的是JdkSerializationRedisSerializer，StringRedisTemplate默认使用的是StringRedisSerializer。
         * Spring Data JPA为我们提供了下面的Serializer：
         * GenericToStringSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer。
         * 在此我们将自己配置RedisTemplate并定义Serializer。
         *
         * @return RedisTemplate redisTemplate
         */
        @Bean
        public RedisTemplate<String, Object> redisTemplate() {
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            // 设置键（key）的序列化采用StringRedisSerializer。
            redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
            redisTemplate.setHashKeySerializer(StringRedisSerializer.UTF_8);
            // 设置默认是为了保证在删除操作误认为set和delete不一致导致删除不掉
            redisTemplate.setDefaultSerializer(StringRedisSerializer.UTF_8);
            // value序列化方式采用jackson
            redisTemplate.setValueSerializer(redisKeySerializer());
            //hash的value序列化方式采用jackson
            redisTemplate.setHashValueSerializer(redisKeySerializer());
            redisTemplate.afterPropertiesSet();
            return redisTemplate;
        }

        public RedisSerializer<?> redisKeySerializer() {
            return new StringRedisSerializer();
        }

        /**
         * 申明缓存管理器，会创建一个切面（aspect）并触发Spring缓存注解的切点（pointcut）
         * 根据类或者方法所使用的注解以及缓存的状态，这个切面会从缓存中获取数据，将数据添加到缓存之中或者从缓存中移除某个值
         * @return
         */
        @Override
        @Bean
        public RedisCacheManager cacheManager() {
            RedisSerializer<String> stringSerializer = new StringRedisSerializer();
            Jackson2JsonRedisSerializer<String> jackson2JsonRedisSerializer = serializer();

            // 配置序列化（解决乱码的问题）,过期时间600秒
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                    // 设置缓存的默认过期时间
                    .entryTtl(Duration.ofSeconds(600))
                    //cacheable key双冒号变为单冒号
                    .computePrefixWith(name -> name+":")
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringSerializer))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                    // 不缓存空值
                    .disableCachingNullValues();
            RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
                    .cacheDefaults(config)
                    .build();
            return cacheManager;
        }

        /**
         * redis数据操作异常处理。该方法处理逻辑：在日志中打印出错误信息，但是放行。
         * 保证redis服务器出现连接等问题的时候不影响程序的正常运行
         */
        @Override
        public CacheErrorHandler errorHandler() {
            return new CacheErrorHandler() {
                @Override
                public void handleCachePutError(RuntimeException exception, Cache cache,
                                                Object key, Object value) {
                    log.error(exception.getMessage(), exception);
                }

                @Override
                public void handleCacheGetError(RuntimeException exception, Cache cache,
                                                Object key) {
                    log.error(exception.getMessage(), exception);
                }

                @Override
                public void handleCacheEvictError(RuntimeException exception, Cache cache,
                                                  Object key) {
                    log.error(exception.getMessage(), exception);
                }

                @Override
                public void handleCacheClearError(RuntimeException exception, Cache cache) {
                    log.error(exception.getMessage(), exception);
                }
            };
        }
        /**
         * 此方法不能用@Ben注解，避免替换Spring容器中的同类型对象
         */
        public Jackson2JsonRedisSerializer<String> serializer(){
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            // 必须配置,否则反序列化得到的是LinkedHashMap对象
            om.activateDefaultTyping(
                    LaissezFaireSubTypeValidator.instance,
                    ObjectMapper.DefaultTyping.NON_FINAL,
                    JsonTypeInfo.As.WRAPPER_ARRAY);
            // 出现未知字段不报错
            om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            return new Jackson2JsonRedisSerializer<>(om,String.class);
        }
}
